﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pipes.Queries.Model.Query;

namespace Pipes.Queries.Execution
{
  public static class OperatorExecution
  {
    public static object Execute(Operator op, params IExpression[] args)
    {
      return Execute(op, (IList<IExpression>)args);
    }
    public static object Execute(Operator op, IList<IExpression> args)
    {
      bool anyNull = false;
      var eArgs = ExecUtils.EvalArgs(args, out anyNull);

      switch (op)
      {
        case Operator.Addition:
          if (anyNull) return DBNull.Value;
          if (ExecUtils.AssertMinCount(args, 1, op.ToString()))
          {
            var root = ExecUtils.GetDecimal(eArgs[0]);
            for (var i = 1; i < eArgs.Count; i++)
            {
              root += ExecUtils.GetDecimal(eArgs[i]);
            }
            return root;
          }
          break;
        case Operator.BitwiseAnd:
          if (anyNull) return DBNull.Value;
          if (ExecUtils.AssertMinCount(args, 1, op.ToString()))
          {
            var root = ExecUtils.GetLong(eArgs[0]);
            for (var i = 1; i < eArgs.Count; i++)
            {
              root &= ExecUtils.GetLong(eArgs[i]);
            }
            return (decimal)root;
          }
          break;
        case Operator.BitwiseNot:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 1, op.ToString());
          return ~ExecUtils.GetLong(eArgs[0]);
        case Operator.BitwiseOr:
          if (anyNull) return DBNull.Value;
          if (ExecUtils.AssertMinCount(args, 1, op.ToString()))
          {
            var root = ExecUtils.GetLong(eArgs[0]);
            for (var i = 1; i < eArgs.Count; i++)
            {
              root |= ExecUtils.GetLong(eArgs[i]);
            }
            return (decimal)root;
          }
          break;
        case Operator.BitwiseXor:
          if (anyNull) return DBNull.Value;
          if (ExecUtils.AssertMinCount(args, 1, op.ToString()))
          {
            var root = ExecUtils.GetLong(eArgs[0]);
            for (var i = 1; i < eArgs.Count; i++)
            {
              root ^= ExecUtils.GetLong(eArgs[i]);
            }
            return (decimal)root;
          }
          break;
        case Operator.Concatenation:
          if (anyNull) return DBNull.Value;
          if (ExecUtils.AssertMinCount(args, 1, op.ToString()))
          {
            var root = eArgs[0].ToString();
            for (var i = 1; i < eArgs.Count; i++)
            {
              root += eArgs[i].ToString();
            }
            return root;
          }
          break;
        case Operator.Division:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 2, op.ToString());
          return ExecUtils.GetDecimal(eArgs[0]) / ExecUtils.GetDecimal(eArgs[1]);
        case Operator.Equals:
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Equals(eArgs[0], eArgs[1], false);
        case Operator.Equals_NullSafe:
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Equals(eArgs[0], eArgs[1], true);
        case Operator.GreaterEqual:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Compare((IComparable)eArgs[0], (IComparable)eArgs[1]) >= 0;
        case Operator.GreaterThan:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Compare((IComparable)eArgs[0], (IComparable)eArgs[1]) > 0;
        case Operator.IntegerDivision:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Math.Truncate(ExecUtils.GetDecimal(eArgs[0]) / ExecUtils.GetDecimal(eArgs[1]));
        case Operator.Is:
          ExecUtils.AssertCount(args, 2, op.ToString());
          return ExecUtils.IsNull(args[0]);
        case Operator.LessEqual:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Compare((IComparable)eArgs[0], (IComparable)eArgs[1]) <= 0;
        case Operator.LessThan:
          if (anyNull) return DBNull.Value;
          ExecUtils.AssertCount(args, 2, op.ToString());
          return Compare((IComparable)eArgs[0], (IComparable)eArgs[1]) < 0;
        // Short circuit evaluation??
        case Operator.LogicalAnd:
          if (anyNull) return DBNull.Value;
          if (eArgs.Count == 1)
          {
            return eArgs[0];
          }
          else if (ExecUtils.AssertMinCount(args, 2, op.ToString()))
          {
            bool result = false;
            foreach (var eArg in eArgs)
            {
              if (ExecUtils.TryGetBool(eArg, out result))
              {
                if (!result) return false;
              }
              else
              {
                return DBNull.Value;
              }
            }
            return true;
          }
          break;
        case Operator.LogicalOr:
          if (anyNull) return DBNull.Value;
          if (eArgs.Count == 1)
          {
            return eArgs[0];
          }
          else if (ExecUtils.AssertMinCount(args, 2, op.ToString()))
          {
            bool result = false;
            foreach (var eArg in eArgs)
            {
              if (ExecUtils.TryGetBool(eArg, out result))
              {
                if (result) return true;
              }
              else
              {
                return DBNull.Value;
              }
            }
            return false;
          }
          break;
      }
      throw new NotImplementedException();
    }

    public static object Equals(object x, object y, bool nullSafe)
    {
      if (ExecUtils.IsNull(x) && ExecUtils.IsNull(y) && nullSafe)
      {
        return true;
      }
      else if (ExecUtils.IsNull(x) || ExecUtils.IsNull(y))
      {
        return DBNull.Value;
      }
      else if (x.GetType() == y.GetType())
      {
        if (x is string && y is string)
        {
          return (string.Compare((string)x, (string)y, StringComparison.InvariantCultureIgnoreCase) == 0);
        }
        else
        {
          return (x == y);
        }
      }
      else
      {
        return false;
      }
    }
    private static int Compare(IComparable x, IComparable y)
    {
      if (x is string && y is string)
      {
        return string.Compare((string)x, (string)y, StringComparison.InvariantCultureIgnoreCase);
      }
      else
      {
        return x.CompareTo(y);
      }
    }
  }
}
